/*
 * app.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version app.c 1.0.0
 * @package libbspapp
 * @author Dr.NP <mp@bsgroup.org>
 * @update 05/18/2011
 */

/**
 * Application (Server) loader & manager
 *
 * === CHANGELOG ===
 * [05/18/2011] - Creation
 */

#include "bsp.h"

#include "bsp_app.h"
#include "bsp_core.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <dlfcn.h>
#include <sys/types.h>

#include <lua.h>
#include <lualib.h>

struct app_t **app_list;
size_t app_list_size;
int app_total;

struct app_t **free_app_list;
size_t free_app_list_size;
int free_app_total;

struct bsp_extension_t **ext_list;
size_t ext_list_size;
int ext_total;

struct bsp_extension_t **free_ext_list;
size_t free_ext_list_size;
int free_ext_total;

void app_param(struct app_t *, const char *, const char *);

int app_init(char *script_filename)
{
    static char ext_filename[_POSIX_PATH_MAX], ext_defination[MAX_FUNC_NAME_LENGTH];
    conf_set("EXT_BASEDIR", DEFAULT_EXT_BASEDIR, CONF_LEVEL_INIT);
    conf_set("APP_BASEDIR", DEFAULT_SCRIPT_BASEDIR, CONF_LEVEL_INIT);
    
    int sid = new_script(), res, nidx, i, j, k, ext_id, app_id, srv_id;
    const char *key, *value;
    struct script_t *s = get_script(sid), *app_script;
    struct bsp_extension_t *e, *try_load;
    struct app_t *a;
    struct server_t *srv;
    void *sofd;

    if (!s)
    {
        fprintf(stderr, "Error script %d\n", sid);
        exit(RTN_ERROR_FATAL);
    }

    s->is_conf = 1;
    res = load_script(s, script_filename);

    if (res != RTN_SUCCESS)
    {
        // App conf script error
        debug_info("Cannot load app defination file <%s>\n", script_filename);
        exit(RTN_ERROR_SCRIPT);
    }

    app_list = ensure_list_space(NULL, sizeof(struct app_t *), &app_list_size, APP_LIST_INITIAL);
    free_app_list = ensure_list_space(NULL, sizeof(struct app_t *), &free_app_list_size, APP_LIST_INITIAL);
    ext_list = ensure_list_space(NULL, sizeof(struct bsp_extension_t *), &ext_list_size, APP_LIST_INITIAL);
    free_ext_list = ensure_list_space(NULL, sizeof(struct bsp_extension_t *), &free_ext_list_size, APP_LIST_INITIAL);
    
    if (!app_list || !free_app_list || !ext_list || !free_ext_list)
    {
        err(RTN_ERROR_MEMORY_ALLOC, "App list error!!!");
    }

    app_total = free_app_total = ext_total = free_ext_total = 0;

    lua_getglobal(s->main_state, APP_PROP_EXT_LIST);
    if (!lua_istable(s->main_state, -1))
    {
        debug_info("Array <%s> does not exists in app defination file <%s>\n", APP_PROP_EXT_LIST, script_filename);
    }

    else
    {
        // Load each module
        nidx = lua_objlen(s->main_state, -1);
        for (i = 1; i <= nidx; i ++)
        {
            lua_rawgeti(s->main_state, -1, i);
            if (lua_isstring(s->main_state, -1))
            {
                value = lua_tostring(s->main_state, -1);

                // Load so
                snprintf(ext_filename, _POSIX_PATH_MAX - 1, "%s/%s", conf_get("EXT_BASEDIR"), value);
                debug_info("Loading extension %s ...\n", ext_filename);

                sofd = dlopen(ext_filename, RTLD_NOW);
                if (!sofd)
                {
                    // File load error
                    debug_info("Loading extension failed : %s\n", dlerror());
                    
                    continue;
                }

                ext_id = new_ext();
                e = get_ext(ext_id);

                if (!e)
                {
                    err(RTN_ERROR_MEMORY_ALLOC, "Extension object nitialize error!!!");
                }
                
                e->entry = NULL;
                e->api = NULL;
                e->ext_name = strdup(value);
                for (j = 0; j < strlen(e->ext_name); j ++)
                {
                    if ('.' == e->ext_name[j])
                    {
                        e->ext_name[j] = 0;
                        break;
                    }
                }

                snprintf(ext_defination, MAX_FUNC_NAME_LENGTH - 1, "bsp_%s", e->ext_name);
                try_load = (struct bsp_extension_t *) dlsym(sofd, ext_defination);

                if (!try_load)
                {
                    debug_info("Symbol <bsp_%s> does not exists in extension %s\n", e->ext_name, ext_filename);
                }

                else
                {
                    e->entry = try_load->entry;
                    e->api = try_load->api;
                }
            }

            lua_pop(s->main_state, 1);
        }
    }

    lua_getglobal(s->main_state, APP_PROP_APP_LIST);
    if (!lua_istable(s->main_state, -1))
    {
        debug_info("Array <%s> does not exists in app defination file <%s>\n", APP_PROP_APP_LIST, script_filename);
    }

    else
    {
        // Load each apps
        nidx = lua_objlen(s->main_state, -1);
        for (i = 1; i <= nidx; i ++)
        {
            lua_rawgeti(s->main_state, -1, i);
            
            if (lua_istable(s->main_state, -1))
            {
                app_id = new_app();
                a = get_app(app_id);

                if (!a)
                {
                    err(RTN_ERROR_MEMORY_ALLOC, "App object initialize failed!");
                }

                //memset(a, 0, sizeof(struct app_t));
                a->script_id = -1;
                
                lua_pushnil(s->main_state);
                while (lua_next(s->main_state, -2))
                {
                    key = lua_tostring(s->main_state, -2);
                    value = lua_tostring(s->main_state, -1);

                    // Each parameter
                    app_param(a, key, value);
                    lua_pop(s->main_state, 1);
                }

                // Load all functions
                app_script = get_script(a->script_id);
                if (app_script && app_script->main_state && 0 == app_script->is_conf)
                {
                    for (j = 0; j < a->napis; j ++)
                    {
                        if (a->apis[j])
                        {
                            e = get_ext_by_name(a->apis[j]);
                            if (e && e->api)
                            {
                                k = 0;
                                while (e->api[k].lua_func && e->api[k].func)
                                {
                                    lua_register(app_script->main_state, e->api[k].lua_func, e->api[k].func);

                                    k++;
                                }
                            }
                        }
                    }
                }

                debug_info("App [%s] loaded\n", a->appname);
                
                // Start server
                srv_id = new_server(a->net | a->transport | a->epoll, a->addr, a->port);

                // On load event
                if (a->entry && a->entry->on_load)
                {
                    a->entry->on_load(app_id);
                }

                if (srv_id >= 0)
                {
                    a->srv_id = srv_id;
                    if (a->entry)
                    {
                        srv = get_server(srv_id);
                        srv->on_conn = a->entry->on_conn;
                        srv->on_close = a->entry->on_close;
                        srv->on_data = a->entry->on_data;

                        srv->app_id = app_id;
                    }
                    
                    core_add_server(srv_id);
                }

                lua_pop(s->main_state, 1);
            }
        }
    }
    
    return RTN_SUCCESS;
}

/**
 * Database version
 */
#ifdef APP_DB
int app_init()
{
    
}
#endif

int new_app()
{
    struct app_t *curr = NULL;

    // Find a unused slot
    if (free_app_total > 0)
    {
        curr = free_app_list[-- free_app_total];
    }

    else
    {
        // No free slots
        while (app_total >= app_list_size)
        {
            app_list = ensure_list_space(app_list, sizeof(struct app_t *), &app_list_size, app_list_size * 2);
            if (!app_list)
            {
                err(RTN_ERROR_MEMORY_ALLOC, "App list error!!!");
            }
        }

        curr = malloc(sizeof(struct app_t));
        memset(curr, 0, sizeof(struct app_t));

        app_list[app_total] = curr;
        curr->app_id = app_total;
    }

    curr->in_use = 0x1;
    app_total ++;

    return curr->app_id;
}

int free_app(int app_id)
{
    struct app_t *a = get_app(app_id);
    int i;
    if (!a)
    {
        return RTN_ERROR_GENERAL;
    }
    
    // Release all running tasks

    // TODO ~~~

    a->in_use = 0;
    a->is_running = 0;

    for (i = 0; i < MAX_API_GROUPS; i ++)
    {
        if (a->apis[i])
        {
            free(a->apis[i]);
        }
    }

    a->napis = 0;

    while (free_app_total >= free_app_list_size)
    {
        free_app_list = ensure_list_space(free_app_list, sizeof(struct app_t *), &free_app_list_size, free_app_list_size * 2);
        if (!free_app_list)
        {
            err(RTN_ERROR_MEMORY_ALLOC, "Free app list error!!!");
        }
    }
    
    free_app_list[free_app_total ++] = a;
    app_list[a->app_id] = NULL;
    app_total --;

    return RTN_SUCCESS;
}

struct app_t * get_app(int app_id)
{
    if (app_id < 0 || app_id >= app_total)
    {
        return NULL;
    }
    
    return app_list[app_id];
}

void app_param(struct app_t *a, const char *key, const char *value)
{
    struct bsp_extension_t *e;
    struct script_t *s;
    
    if (!a)
    {
        // Kidding me...
        return;
    }

    if (0 == strncasecmp(key, "net", 3))
    {
        if (0 == strcasecmp(value, "IPv6"))
        {
            a->net = NET_IPV6;
        }

        else
        {
            a->net = NET_IPV4;
        }
    }

    else if (0 == strncasecmp(key, "epoll", 5))
    {
        if (0 == strcasecmp(value, "LT"))
        {
            a->epoll = EPOLL_MODE_LT;
        }

        else
        {
            a->epoll = EPOLL_MODE_ET;
        }
    }

    else if (0 == strncasecmp(key, "transport", 9))
    {
        if (0 == strcasecmp(value, "UDP"))
        {
            a->transport = TRANSPORT_UDP;
        }

        else if (0 == strcasecmp(value, "LOCAL"))
        {
            a->transport = TRANSPORT_LOCAL;
        }

        else
        {
            a->transport = TRANSPORT_TCP;
        }
    }

    else if (0 == strncasecmp(key, "port", 4))
    {
        a->port = atoi(value);
    }

    else if (0 == strncasecmp(key, "addr", 4))
    {
        snprintf(a->addr, ADDR_DSP_LENGTH - 1, "%s", value);
    }

    else if (0 == strncasecmp(key, "appname", 7))
    {
        snprintf(a->appname, MAX_APP_NAME_LENGTH - 1, "%s", value);
    }

    else if (0 == strncasecmp(key, "entry", 5))
    {
        // Load entries
        e = get_ext_by_name(value);
        if (e)
        {
            a->entry = e->entry;
        }

        else
        {
            debug_info("Cannot get entry!!!!\n");
        }
    }

    else if (0 == strncasecmp(key, "api", 3))
    {
        // Registered functions
        e = get_ext_by_name(value);
        if (e && e->api)
        {
            a->apis[a->napis ++] = strdup(value);
        }
    }

    else if (0 == strncasecmp(key, "luascript", 9))
    {
        // Lua script
        int sid = new_script();
        s = get_script(sid);
        static char script_filename[_POSIX_PATH_MAX];

        if (!s)
        {
            return;
        }

        s->is_conf = 0x0;
        snprintf(script_filename, _POSIX_PATH_MAX - 1, "%s/%s", conf_get("APP_BASEDIR"), value);
        int res = load_script(s, script_filename);
        if (res != RTN_SUCCESS)
        {
            // Load error
            del_script(s);

            return;
        }

        a->script_id = s->id;
    }

    else
    {
        // Invalid parameters
    }
    
    return;
}

int new_ext()
{
    struct bsp_extension_t *curr = NULL;

    // Find a unused slot
    if (free_ext_total > 0)
    {
        curr = free_ext_list[-- free_ext_total];
    }

    else
    {
        // No free slots
        while (ext_total >= ext_list_size)
        {
            ext_list = ensure_list_space(ext_list, sizeof(struct bsp_extension_t *), &ext_list_size, ext_list_size * 2);
            if (!ext_list)
            {
                err(RTN_ERROR_MEMORY_ALLOC, "App list error!!!");
            }
        }

        curr = malloc(sizeof(struct bsp_extension_t));
        memset(curr, 0, sizeof(struct bsp_extension_t));

        ext_list[ext_total] = curr;
        curr->ext_id = ext_total;
    }

    ext_total ++;

    return curr->ext_id;        
}

struct bsp_extension_t * get_ext(int ext_id)
{
    if (ext_id < 0 || ext_id >= ext_total)
    {
        return NULL;
    }

    return ext_list[ext_id];
}

struct bsp_extension_t * get_ext_by_name(const char *ext_name)
{
    int i;

    for (i = 0; i < ext_total; i ++)
    {
        if (ext_list[i] && ext_list[i]->ext_name && 0 == strcmp(ext_list[i]->ext_name, ext_name))
        {
            return ext_list[i];
        }
    }

    return NULL;
}
